Skip to content

fix(serve): parse tool calls with empty/omitted argument markers in ToolParser - #57

Open
Ultron09 wants to merge 1 commit into
sqliteai:mainfrom
Ultron09:fix/kimitools-no-arg-call
Open

fix(serve): parse tool calls with empty/omitted argument markers in ToolParser#57
Ultron09 wants to merge 1 commit into
sqliteai:mainfrom
Ultron09:fix/kimitools-no-arg-call

Conversation

@Ultron09

Copy link
Copy Markdown
Contributor

Summary

When Kimi / DeepSeek models generate parameterless tool calls or tool calls without explicit arguments (e.g. <|tool_call_begin|>functions.get_time:0<|tool_call_end|>), or when a streaming reply terminates while in the header state before <|tool_call_argument_begin|>, ToolParser previously bypassed _parse_header() because header parsing was only triggered upon receiving _ARG_BEGIN.

As a result, any tool call lacking an argument block was silently discarded from self.calls when _CALL_END or _SECTION_END arrived.

Changes

  1. serve/kimitools.py:
    • In ToolParser.feed_marker(): Check if self._state == "header" when receiving _CALL_END or _SECTION_END, parse the header into a ToolCall, append to self.calls, and finalize arguments (json_block).
    • In ToolParser.finish(): If the stream terminates while in "header" state, flush and parse the pending header into self.calls.
  2. serve/engine.py:
    • In version() and build_info(): Catch EngineError when libwaste shared library is unbuilt so server health endpoints and test runners degrade gracefully.
  3. tests/serve/test_chatfmt.py:
    • Added unit tests:
      • test_kimi_tool_call_without_arguments_marker
      • test_kimi_tool_call_stream_ended_in_header

Verification

  • Ran python -m unittest tests.serve.test_chatfmt (51/51 tests pass).
  • Ran all 244 serve tests via python -m unittest discover -s tests/serve -t . -p "test_*.py" (244/244 OK).

…oolParser

When a tool call contains no arguments (e.g. <|tool_call_begin|>functions.func:0<|tool_call_end|>) or when a stream ends in header state, _parse_header() was previously bypassed because ARG_BEGIN was not encountered, causing the tool call to be silently dropped.

Also catch EngineError in version() and build_info() when libwaste is unbuilt so server health endpoints and test runners degrade gracefully.

Add regression tests in tests/serve/test_chatfmt.py.

@mfethe1 mfethe1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated on macOS 15.6 / arm64 against head cce7c67.

Full suite on PR head: python3 -m pytest tests/serve/test_chatfmt.py -q51 passed (main's 49 + the 2 new).

Must-bite control: reverted only serve/ to main (c66c7b3), kept the new tests → the 2 new tests FAIL on unpatched code (test_kimi_tool_call_without_arguments_marker, test_kimi_tool_call_stream_ended_in_header) while all 49 pre-existing still pass. The tests genuinely pin the fix, not the fixture.

Edge probes on PR head (beyond the author's tests):

  • Two no-arg calls back-to-back → [('get_time',0,''), ('ping',1,'')] — no duplication, indexes correct.
  • section_end while in header state after a prior complete call → no double-append of the earlier call (the self._current is not None guard ordering is right).
  • Non-int index suffix (functions.get_time:abc) → name parsed, index falls back to positional 0 — pre-existing _parse_header semantics preserved.

Nit (non-blocking): <|tool_call_begin|> immediately followed by <|tool_call_end|> (empty header) now emits a call with name='' rather than dropping it silently. Arguably correct (the model did emit a call marker), but worth a one-line decision; OpenAI-compat consumers may prefer arguments: {}. Not a merge blocker for me.

engine.py note: the version()/build_info() EngineError → "unknown"/"unbuilt" change is behavior-preserving for built engines; it converts a hard-fail into a sentinel for unbuilt engines — consistent with how run.sh distinguishes engine-missing from engine-wrong. No regression observed.

Verdict: approve — minimal three-site state-machine patch (_CALL_END, _SECTION_END, finish()), tests bite on unpatched code, no regression in the surrounding 49.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants